/*
 * This file is part of JContextFree.
 *
 * JContextFree is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JContextFree is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JContextFree.  If not, see <http://www.gnu.org/licenses/>.
 */

package it.enricod.jcontextfree.engine.model.impl;

import it.enricod.jcontextfree.engine.model.DataModelFactory;
import it.enricod.jcontextfree.engine.model.IFigure;
import it.enricod.jcontextfree.engine.model.IFigureParameters;
import it.enricod.jcontextfree.engine.model.IPaint;
import it.enricod.jcontextfree.engine.model.IProgram;
import it.enricod.jcontextfree.engine.model.IRuleDef;
import it.enricod.jcontextfree.engine.model.IShape;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.apache.log4j.Logger;

public class Program implements IProgram
{

	private Logger logger = Logger.getLogger(Program.class);
	/**
	 * il nome della regola di partenza
	 */
	private final String startshape;
	/**
	 * il codice del programma
	 */
	private String text;
	private Map<String, List<IRuleDef>> ruleDefinitionMap = new LinkedHashMap<String, List<IRuleDef>>();
	private DataModelFactory dataModelFactory = new DataModelFactory();

	/**
	 * 
	 * @param startshape
	 *            nome del parametro startshape
	 */
	public Program(String startshape)
	{
		this.startshape = startshape;
	}

	public IRuleDef getStartRuleDef()
	{
		List<IRuleDef> ruleDefList = ruleDefinitionMap.get(startshape);
		return ruleDefList.get(0);
	}

	public void addRuleDef(String name, IRuleDef ruleDef)
	{
		if (ruleDefinitionMap.get(name) == null)
		{
			ruleDefinitionMap.put(name, new ArrayList<IRuleDef>());
		}
		this.ruleDefinitionMap.get(name).add(ruleDef);
	}

	public void setText(String text)
	{
		this.text = text;
	}

	public String getAsText()
	{
		return text;
	}

	public IPaint createPaint()
	{
		ShapeParams shapeParams = new ShapeParams();

		IPaint result = dataModelFactory.createPaint();
		IRuleDef startRule = getStartRuleDef();
		parseRule(startRule, result, shapeParams);
		return result;
	}

	private void parseRule(IRuleDef ruleDef, IPaint paint, ShapeParams shapeParams)
	{
		List<IShape> shapes = ruleDef.getShapes();
		for (IShape shape : shapes)
		{
			double shapeB = shape.getAttributeAsDouble("b", 0d);
			double shapeF = shape.getAttributeAsDouble("f", 0d);

			ShapeParams shapeParams2 = shapeParams.getClone();
			shapeParams2.s = shape.getAttributeAsDouble("s", 1.0);
			shapeParams2.x = shape.getAttributeAsDouble("x", 0d);
			shapeParams2.y = shape.getAttributeAsDouble("y", 0d);
			shapeParams2.r = shape.getAttributeAsDouble("r", 0d);

			if (shape.isPrimitive())
			{
				// se e' primitive la possiamo disegnare immediatamente
				IFigure figure = dataModelFactory.createNewFigure(shape.getName());

				IFigureParameters params = figure.getParameters();
				params.getRelative().setX(shapeParams2.x);
				params.getRelative().setY(shapeParams2.y);
				
				params.getTransform().setX(shapeParams.contextX);
				params.getTransform().setY(shapeParams.contextY);
				params.getRelative().setRotation(shapeParams2.r);
				params.getTransform().setRotation(shapeParams.contextR);
				params.getRelative().setBrightness(shapeB);
				params.getTransform().setBrightness(shapeParams.contextB);
				params.getRelative().setFlip(shapeF);
				params.getTransform().setFlip(shapeParams.contextF);
				params.getRelative().setScale(shapeParams2.s);
				params.getTransform().setScale(shapeParams.contextS);

				logger.debug(figure.toString());
				paint.addFigure(figure);
			} else
			{
				IRuleDef aRuleDef = getRuleDef(shape.getName());

				shapeParams2.contextX = shapeParams.contextX + shapeParams.x * shapeParams.contextS;
				shapeParams2.contextY = shapeParams.contextY + shapeParams.y * shapeParams.contextS;
				shapeParams2.contextR = shapeParams2.r * shapeParams2.r;
				shapeParams2.contextS = shapeParams2.s * shapeParams2.contextS;

				parseRule(aRuleDef, paint, shapeParams2);
			}
		}
	}

	private IRuleDef getRuleDef(String ruleName)
	{
		// elenco delle regole canditate
		List<IRuleDef> rules = ruleDefinitionMap.get(ruleName);
		if (rules.size() == 0)
		{
			throw new IllegalStateException("regola non esistente: " + ruleName);
		}
		if (rules.size() == 1)
		{
			return rules.get(0);
		}
		// se ne esiste piu' di una, scegliamo a caso
		// TODO gestire le probabilita'
		Random generator = new Random();
		int rnd = generator.nextInt(rules.size());
		return rules.get(rnd);
	}

	private class ShapeParams
	{
		public double x = 0d;
		public double y = 0d;
		public double r = 0d;
		public double s = 1.0d;
		public double b = 0d; // brightness
		public double f = 0d; // flip
		public double contextX = 0d;
		public double contextY = 0d;
		public double contextR = 0d;
		public double contextS = 1.0d; // coefficiente di riduzione della
		// dimensione
		public double contextB = 0d; // brightness
		public double contextF = 0d; // flip

		ShapeParams getClone()
		{
			ShapeParams result = new ShapeParams();
			result.b = this.b;
			result.contextB = this.contextB;
			result.contextF = this.contextF;
			result.contextR = this.contextR;
			result.contextS = this.contextS;
			result.contextX = this.contextX;
			result.contextY = this.contextY;
			result.f = this.f;
			result.r = this.r;
			result.s = this.s;
			result.x = this.x;
			result.y = this.y;
			return this;
		}
	}
}
